#include "common.h"
#include "Vmulti_fifo.h"
#define MAX_WAVE 10000
#define TRACE 1

#define PORT_NUM 4
Vmulti_fifo top;
VerilatedFstC *tfp;
static uint64_t dump_time = 0;
uint64_t cycle = 0;
void dump()
{
#if TRACE
    if (dump_time < MAX_WAVE)
    {
        tfp->dump(dump_time++);
    }
#endif
}

void run()
{

    top.clk = 0;
    top.eval();
    dump();
    cycle++;
    top.clk = 1;
    top.eval();
    dump();
}

void reset()
{
    top.rst = 1;
    run();
    run();
    run();
    top.rst = 0;
}

bool push(uint64_t data, uint32_t index)
{
    top.i_valid = 1;
    top.i_data_valid[index] = 1;
    top.i_data[index] = data;
    if (top.i_ready & top.i_valid)
    {
        // LOG("push %ld",data);
        return true;
    }
    else
    {
        return false;
    }
}
uint64_t front_data(uint32_t index)
{
    return top.o_data[index];
}
uint64_t front_valid(uint32_t index)
{
    return top.o_data_valid[index] & top.o_data_ready[index];
}
bool pop(uint32_t index)
{
    top.o_ready = 1;
    top.o_data_ready[index] = 1;
    if (top.o_ready & top.o_valid)
    {
        // LOG("pop  %ld",front());
        return true;
    }
    else
    {
        return false;
    }
}

void fifo_test(uint32_t r1, uint32_t r2)
{
    LOG("test full push %d%% pop %d%% at %ld", r1, r2, dump_time);
    reset();
    int test_cnt = 1000000;
    uint64_t push_data = 0;
    uint64_t pop_data = 0;
    while (test_cnt--)
    {
        top.i_valid = 0;
        for (size_t i = 0; i < PORT_NUM; i++)
        {
            top.i_data_valid[i] = 0;
            top.o_data_ready[i] = 0;
        }

        top.o_ready = 0;
        uint32_t r = rand();
        r %= 100;
        uint32_t push_num = rand();
        push_num %= PORT_NUM;
        if (r < r1)
        {
            for (size_t i = 0; i < push_num; i++)
            {
                bool success = push(push_data, i);
                if (success)
                {
                    push_data++;
                }
                else
                {
                    break;
                }
            }
        }
        r = rand();
        r %= 100;
        uint32_t pop_num = rand();
        pop_num %= PORT_NUM;
        if (r < r2)
        {
            for (size_t i = 0; i < pop_num; i++)
            {
                bool success = pop(i);
                if (success)
                {
                    if (!front_valid(i))
                    {
                        for (; i < PORT_NUM; i++)
                        {
                            if (front_valid(i))
                            {
                                LOG("pop valid not align at %ld", dump_time);
                            }
                        }
                        break;
                    }
                    if (pop_data != front_data(i))
                    {
                        LOG("pop data : %ld != o_data : %ld at %ld", pop_data, front_data(i), dump_time);
                        return;
                    }
                    else
                    {
                        // LOG("Pop[%ld] %ld at %ld",i,front_data(i),dump_time);
                    }
                    pop_data++;
                }
            }
        }
        run();
    }
    LOG("test done");
}

int main()
{
    srand(0);

#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top.trace(tfp, 0);
    tfp->open("Vmulti_fifo.fst");
#endif
    reset();
    uint32_t test_loop = 10;
    while (test_loop--)
    {

        fifo_test(100, 1);
        fifo_test(1, 100);
        fifo_test(100, 10);
        fifo_test(10, 100);
        fifo_test(100, 100);
        fifo_test(50, 50);
        fifo_test(20, 50);
        fifo_test(50, 20);
        fifo_test(10, 5);
        fifo_test(5, 10);
        uint32_t rand_loop = 10;
        while (rand_loop--)
        {
            uint32_t r1 = rand();
            uint32_t r2 = rand();
            fifo_test(r1 % 100, r2 % 100);
        }
    }

#if TRACE
    dump();
    tfp->close();
    delete tfp;
#endif
    LOG("All test done");
    return 0;
}